using System;
using Microsoft.Xna.Framework;


namespace Core.GameObjects.Steering
{
    public class SteeringBehaviors
    {
        Random rand;

        MovingGameObject _Owner;

        float _WanderRadius = 1.2f;
        float _WanderDistance = 2.0f;
        float _WanderJitter = 80.0f;

        Vector2 _WanderTarget = Vector2.Zero;
        Vector2 _SteeringForce = Vector2.Zero;

        public SteeringBehaviors(MovingGameObject Owner)
        {
            rand = new Random();
            _Owner = Owner;
        }

        public void SeekOn() { _SeekOn = true; }
        public void SeekOff() { _SeekOn = false; }
        protected bool _SeekOn = false;

        public Vector2 Seek(Vector2 TargetPosition)
        {
            //Calculate the velocity required to Seek the target, scaled to the length of the max speed
            Vector2 DesiredVelocity = Vector2.Normalize(TargetPosition - _Owner.Position) * _Owner.MaxSpeed;
            //The force returned is the whats required, when added to the current velocity, to reach the target.
            return DesiredVelocity - _Owner.Velocity;
        }

        public void FleeOn() { _FleeOn = true; }
        public void FleeOff() { _FleeOn = false; }
        protected bool _FleeOn = false;

        public Vector2 Flee(Vector2 TargetPosition)
        {
            //Calculate the velocity required to Flee the target, scaled to the length of the max speed
            Vector2 DesiredVelocity = Vector2.Normalize(_Owner.Position - TargetPosition) * _Owner.MaxSpeed;
            //The force returned is the whats required, when added to the current velocity, to reach the target.
            return DesiredVelocity - _Owner.Velocity;
        }

        public enum Deceleration { slow = 3, normal = 2, fast = 1 };

        public void ArriveOn() { _ArriveOn = true; }
        public void ArriveOff() { _ArriveOn = false; }
        protected bool _ArriveOn = false;

        public Vector2 Arrive(Vector2 TargetPosition, Deceleration Deceleration)
        {
            Vector2 ToTarget = TargetPosition - _Owner.Position;

            //Calculate distance to target
            float dist = ToTarget.Length();

            if (dist > 0)
            {
                //Because deceleration is an int we need this for fine tweaking
                const float DecelerationTweaker = 0.3f;

                //Calculate the speed required to reach the to reach the target given the desired deceleration
                float speed = dist / ((float)DecelerationTweaker * DecelerationTweaker);

                //Make sure speed does not surpass maxspeed. By using min we just get whichever speed is slower
                speed = Math.Min(speed, _Owner.MaxSpeed);

                //Here we proceed just like with Seek, except we dont' need to normalize cause we have already calculated it's length.
                Vector2 DesiredVelocity = ToTarget * speed / dist;

                return (DesiredVelocity - _Owner.Velocity);
            }

            return Vector2.Zero;
        }

        public void PursuitOn() { _PursuitOn = true; }
        public void PursuitOff() { _PursuitOn = false; }
        protected bool _PursuitOn = false;

        public Vector2 Pursuit(MovingGameObject Evader)
        {
            //If the evader is ahead and facing us then we can just seek for the evaders current position
            Vector2 ToEvader = Evader.Position - _Owner.Position;

            float RelativeHeading = Vector2.Dot(_Owner.Heading, Evader.Heading);

            if (Vector2.Dot(ToEvader, _Owner.Heading) > 0 && RelativeHeading > -0.95) //acos(0.95) = 18 deg
            {
                return Seek(Evader.Position);
            }

            //Evader is not considered ahead of us so we predict where the evader will be
            //The Look-ahead time is proportional to the distance between the evader and the pursuer; 
            //and is inversely proportional to the sum of the agents' veocities
            float LookAheadTime = ToEvader.Length() / (_Owner.MaxSpeed + Evader.Velocity.Length());

            //Now seek to the predicted future position of the evader
            return Seek(Evader.Position + Evader.Velocity * LookAheadTime);
        }

        public void EvadeOn() { _EvadeOn = true; }
        public void EvadeOff() { _EvadeOn = false; }
        protected bool _EvadeOn = false;

        public Vector2 Evade(MovingGameObject Pursuer)
        {
            Vector2 ToPursuer = Pursuer.Position - _Owner.Position;

            //Look Ahead Time
            float LookAheadTime = ToPursuer.Length() / (_Owner.MaxSpeed + Pursuer.Velocity.Length());

            //Now flee from pursuer
            return Flee(Pursuer.Position + Pursuer.Velocity * LookAheadTime);
        }


        public void WanderOn() { _WanderOn = true; }
        public void WanderOff() { _WanderOn = false; }
        protected bool _WanderOn = false;

        public Vector2 Wander()
        {
            //First add a small random vector to the target's position
            _WanderTarget += new Vector2(RandomToScaled(rand.NextDouble(), 1) * _WanderJitter,
                                        RandomToScaled(rand.NextDouble(), 1) * _WanderJitter);
            //Reproject  this new vector back onto the Unit Circle
            _WanderTarget.Normalize();

            //Increase the length of the vector to the same as the radius of the wander circle.
            _WanderTarget *= _WanderRadius;

            //Move the target into a position WanderDistance in front of the agent
            Vector2 TargetLocal = _WanderTarget + new Vector2(_WanderDistance, 0);

            //Project the target into world space
            Vector2 TargetWorld = Vector2.Zero; //TODO: Calculate point in world space Here

            //Steer towards the target position
            return TargetWorld - _Owner.Position;
        }

        private float RandomToScaled(double r, double maximum)
        {
          return (float)((r - 0.5) * 2 * maximum);
        }
        // This will return a number between -5000 and 5000.
        //RandomToScaled(random.NextDouble(), 5000);


        public Vector2 Update(float timeElapsed)
        {
            return new Vector2();
        }

        public Vector2 Calculate()
        {
            return new Vector2();
        }

        protected bool AccumulateForce(ref Vector2 RunningTotal, Vector2 ForceToAdd)
        {
            return true;
        }
    }
}
